/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.read.biff;

import jxl.WorkbookSettings;
import jxl.biff.BaseCompoundFile;
import jxl.biff.IntegerHelper;
import jxl.biff.Type;
import jxl.common.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;

/**
 * File containing the data from the binary stream
 */
public class File {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(File.class);

    /**
     * The data from the excel 97 file
     */
    private byte[] data;
    /**
     * The current position within the file
     */
    private int filePos;
    /**
     * The saved pos
     */
    private int oldPos;
    /**
     * The initial file size
     */
    private int initialFileSize;
    /**
     * The amount to increase the growable array by
     */
    private int arrayGrowSize;
    /**
     * A handle to the compound file. This is only preserved when the
     * copying of PropertySets is enabled
     */
    private CompoundFile compoundFile;
    /**
     * The workbook settings
     */
    private WorkbookSettings workbookSettings;

    /**
     * Constructs a file from the input stream
     *
     * @param is the input stream
     * @param ws the workbook settings
     * @exception IOException
     * @exception BiffException
     */
    public File(InputStream is, WorkbookSettings ws)
            throws IOException, BiffException {
        // Initialize the file sizing parameters from the settings
        workbookSettings = ws;
        initialFileSize = workbookSettings.getInitialFileSize();
        arrayGrowSize = workbookSettings.getArrayGrowSize();

        byte[] d = new byte[initialFileSize];
        int bytesRead = is.read(d);
        int pos = bytesRead;

        // Handle thread interruptions, in case the user keeps pressing
        // the Submit button from a browser.  Thanks to Mike Smith for this
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedIOException();
        }

        while (bytesRead != -1) {
            if (pos >= d.length) {
                // Grow the array
                byte[] newArray = new byte[d.length + arrayGrowSize];
                System.arraycopy(d, 0, newArray, 0, d.length);
                d = newArray;
            }
            bytesRead = is.read(d, pos, d.length - pos);
            pos += bytesRead;

            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedIOException();
            }
        }

        bytesRead = pos + 1;

        // Perform file reading checks and throw exceptions as necessary
        if (bytesRead == 0) {
            throw new BiffException(BiffException.excelFileNotFound);
        }

        CompoundFile cf = new CompoundFile(d, ws);
        try {
            data = cf.getStream("workbook");
        } catch (BiffException e) {
            // this might be in excel 95 format - try again
            data = cf.getStream("book");
        }

        if (!workbookSettings.getPropertySetsDisabled() &&
                (cf.getNumberOfPropertySets() >
                        BaseCompoundFile.STANDARD_PROPERTY_SETS.length)) {
            compoundFile = cf;
        }

        cf = null;

        if (!workbookSettings.getGCDisabled()) {
            System.gc();
        }

        // Uncomment the following lines to send the pure workbook stream
        // (ie. a defragged ole stream) to an output file

//      FileOutputStream fos = new FileOutputStream("defraggedxls");
//      fos.write(data);
//      fos.close();

    }

    /**
     * Constructs a file from already defragged binary data.  Useful for
     * displaying subportions of excel streams.  This is only used during
     * special runs of the "BiffDump" demo program and should not be invoked
     * as part of standard JExcelApi parsing
     *
     * @param d the already parsed data
     */
    public File(byte[] d) {
        data = d;
    }

    /**
     * Returns the next data record and increments the pointer
     *
     * @return the next data record
     */
    Record next() {
        Record r = new Record(data, filePos, this);
        return r;
    }

    /**
     * Peek ahead to the next record, without incrementing the file position
     *
     * @return the next record
     */
    Record peek() {
        int tempPos = filePos;
        Record r = new Record(data, filePos, this);
        filePos = tempPos;
        return r;
    }

    /**
     * Skips forward the specified number of bytes
     *
     * @param bytes the number of bytes to skip forward
     */
    public void skip(int bytes) {
        filePos += bytes;
    }

    /**
     * Copies the bytes into a new array and returns it.
     *
     * @param pos the position to read from
     * @param length the number of bytes to read
     * @return The bytes read
     */
    public byte[] read(int pos, int length) {
        byte[] ret = new byte[length];
        try {
            System.arraycopy(data, pos, ret, 0, length);
        } catch (ArrayIndexOutOfBoundsException e) {
            logger.error("Array index out of bounds at position " + pos +
                    " record length " + length);
            throw e;
        }
        return ret;
    }

    /**
     * Gets the position in the stream
     *
     * @return the position in the stream
     */
    public int getPos() {
        return filePos;
    }

    /**
     * Saves the current position and temporarily sets the position to be the
     * new one.  The original position may be restored usind the restorePos()
     * method. This is used when reading in the cell values of the sheet - an
     * addition in 1.6 for memory allocation reasons.
     *
     * These methods are used by the SheetImpl.readSheet() when it is reading
     * in all the cell values
     *
     * @param p the temporary position
     */
    public void setPos(int p) {
        oldPos = filePos;
        filePos = p;
    }

    /**
     * Restores the original position
     *
     * These methods are used by the SheetImpl.readSheet() when it is reading
     * in all the cell values
     */
    public void restorePos() {
        filePos = oldPos;
    }

    /**
     * Moves to the first bof in the file
     */
    private void moveToFirstBof() {
        boolean bofFound = false;
        while (!bofFound) {
            int code = IntegerHelper.getInt(data[filePos], data[filePos + 1]);
            if (code == Type.BOF.value) {
                bofFound = true;
            } else {
                skip(128);
            }
        }
    }

    /**
     * "Closes" the biff file
     *
     * @deprecated As of version 1.6 use workbook.close() instead
     */
    public void close() {
    }

    /**
     * Clears the contents of the file
     */
    public void clear() {
        data = null;
    }

    /**
     * Determines if the current position exceeds the end of the file
     *
     * @return TRUE if there is more data left in the array, FALSE otherwise
     */
    public boolean hasNext() {
        // Allow four bytes for the record code and its length
        return filePos < data.length - 4;
    }

    /**
     * Accessor for the compound file.  The returned value will only be non-null
     * if the property sets feature is enabled and the workbook contains
     * additional property sets
     *
     * @return the compound file
     */
    CompoundFile getCompoundFile() {
        return compoundFile;
    }
}
